<html>
    <head>
        <title>使用上下文深入传递数据</title>
    </head>
    <body>
        <script>
            // 使用上下文深入传递数据
                     /*
                         通常，你会通过属性将信息从父组件传递到子组件。
                         但是如果你必须在中间通过多个组件传递数据，或者如果你的应用中的许多组件需要相同的信息，那么传递属性会变得冗长和不方便。
                         上下文(Context)允许父组件想其下面的树中的任何组件提供一些信息(无论多深)，而无需通过属性显式传递。
                     */

                     // 一、传递属性的问题
                               /*
                                    "传递属性"是通过UI树将数据"显式"传输到使用它的组件的好方法。

                                    但是当你需要通过树深入传递一些属性时，或者如果许多组件需要相同的属性时，传递属性会变得冗长和不方便。
                                    最近的共同祖级可能远离需要数据的组件，那么高的"状态提升"可能导致称为"属性钻井"的情况。

                                    如果有一种方法可以在不传递属性的情况下"远距离传递"数据给树中需要它的组件，那不是很好吗？
                                    有了React的上下文功能，就有了！
                               */


                    // 二、上下文：  传递属性的替代方案

                            /*
                                上下文让父组件向其下发的整个树提供数据。
                                上下文很有用处。
                            */

                                 // eg:
                                    // Heading.js 文件：
                                        export default function Heading({ level, children }) {
                                            switch (level) {
                                                case 1:
                                                return <h1>{children}</h1>;
                                                case 2:
                                                return <h2>{children}</h2>;
                                                case 3:
                                                return <h3>{children}</h3>;
                                                case 4:
                                                return <h4>{children}</h4>;
                                                case 5:
                                                return <h5>{children}</h5>;
                                                case 6:
                                                return <h6>{children}</h6>;
                                                default:
                                                throw Error('Unknown level: ' + level);
                                            }
                                        }

                                    // Section.js 文件：
                                        export default function Section({ children }) {
                                            return (
                                                <section className="section">
                                                    {children}
                                                </section>
                                            );
                                        }

                                    // app.js 文件：
                                        import Heading from './Heading.js';
                                        import Section from './Section.js';
                                        export default function Page() {
                                            return (
                                                <Section>
                                                    <Heading level={1}>Title</Heading>
                                                    <Heading level={2}>Heading</Heading>
                                                    <Heading level={3}>Sub-heading</Heading>
                                                    <Heading level={4}>Sub-sub-heading</Heading>
                                                    <Heading level={5}>Sub-sub-sub-heading</Heading>
                                                    <Heading level={6}>Sub-sub-sub-sub-heading</Heading>
                                                </Section>
                                            );
                                        }

                            // 假设，你希望同一个Section中的多个标题始终具有相同的大小：
                                    import Heading from './Heading.js';
                                    import Section from './Section.js';

                                    export default function Page() {
                                        return (
                                            <Section>
                                            <Heading level={1}>Title</Heading>
                                            <Section>
                                                <Heading level={2}>Heading</Heading>
                                                <Heading level={2}>Heading</Heading>
                                                <Heading level={2}>Heading</Heading>
                                                <Section>
                                                <Heading level={3}>Sub-heading</Heading>
                                                <Heading level={3}>Sub-heading</Heading>
                                                <Heading level={3}>Sub-heading</Heading>
                                                <Section>
                                                    <Heading level={4}>Sub-sub-heading</Heading>
                                                    <Heading level={4}>Sub-sub-heading</Heading>
                                                    <Heading level={4}>Sub-sub-heading</Heading>
                                                </Section>
                                                </Section>
                                            </Section>
                                            </Section>
                                        );
                                    }
                                    // 目前，你将level属性分别传递给每个<Heading/>组件

                            /*
                                如果你可以将 level 属性传递给<Section/>组件，并将其从<Heading/>组件中删除，那就太好了。
                                这样你就可以强制同意部分中的所有标题具有相同的大小：
                                         <Section level={2}>
                                            <Heading>About</Heading>
                                            <Heading>Photos</Heading>
                                            <Heading>Videos</Heading>
                                         </Section>

                                但是，<Heading>组件如何知道其最接近的<Section>的级别呢？  这将需要某种方式让子级"访问"获取树上某处的数据。
                                光靠属性是办不到的。
                                这就是"上下文"发挥作用的地方。你将分三步完成：
                                        ①、创建一个上下文。（你可以将其称为 LevelContext）
                                        ②、使用需要数据的组件中的上下文.(heading 将使用LevelContext)
                                        ③、从指定数据的组件提供上下文(Section 将提供LevelContext)

                                    "上下文"让父级---即使是远方的父级！---向其中的整棵树提供数据。
                            */



                            // 1、步骤1：创建上下文：
                                    /*
                                        首先，你需要创建上下文。你需要从文件中导出它，以便你的组件可以使用它：
                                    */

                                    // 在 LevelContext.js文件中：
                                        import {createContext} from 'react';
                                        export const LevelContext = createContext(1);
                                        // craeteContext()的"唯一参数" 是默认值。   在这里1 指的是最大的标题级别，但你可以传递任何类型的值(甚至是对象)

                            // 2、步骤2： 使用上下文：
                                    // 从React和你的上下文中导入 useContext钩子
                                        import {useContext} from 'react';
                                        import {LevelContext} from './LevelContext.js';

                                    /*
                                        之前，<Heading> 组件从属性中读取 level:
                                            export default function Heading({level,children}){
                                                //.....
                                            }
                                    */
                                    // 现在，它将从上下文中读取 level:
                                            export default function Heading({children}){
                                                const level = useContext(LevelContext);
                                                // .....
                                            }

                                    /*
                                        useContext 是一个钩子(Hooks).
                                        就像useState 和 useReducer 一样，你只能在React组件内立即调用钩子(不能循环调用，和条件调用钩子)。

                                        useContext 告诉React <Heading>组件想要读取 LevelContext这个上下文对象中的值。
                                    */
                                    /*
                                             现在， Heading 组件没有 level属性了，你不需要再将 level属性传递给它了:
                                                        <Section level={4}>
                                                                <Heading>Sub-sub-heading</Heading>
                                                                <Heading>Sub-sub-heading</Heading>
                                                                <Heading>Sub-sub-heading</Heading>
                                                        </Section>
                                    */ 

                                    // 注意，此时此示例还不能工作！ 虽然你在<Heading>组件中使用了LevelContext上下文变量，但是你还没有提供这个上下文。 React不知道从哪里得到它。
                                    /*
                                        如果你"不想提供上下文"，React将使用你在上一步中指定默认值。
                                        在此实例中，你将1指定为 createContext 的参数，因此 useContext(LevelContext)返回1，
                                    */

                            // 3、步骤3：提供上下文
                                    // <Section>组件当前渲染其子级：
                                        export default function Section({ children }) {
                                            return (
                                                <section className="section">
                                                {children}
                                                </section>
                                            );
                                        }

                                    // 现在，用上下文提供程序封装它们以向他们提供LevelContext:
                                        import {levelContext } from './LevelContext.js'
                                        export default function Section({level,children}){
                                            return(
                                                <Section className="section">
                                                    <LevelContext.Provider value={level}>
                                                        {children}
                                                    </LevelContext.Provider>
                                                </Section>
                                            );
                                        }
                                    /*
                                        这告诉React： "如果<Section>内的任何组件需要LevelContext,请给它们这个level"。
                                                    该组件将使用其上方UI树最近的<LevelContext.Provider>提供的值。
                                    */
                                        // 使用上下文修改后的代码如下：
                                           // LevelContext.js
                                                import {createContext} from 'react';
                                                export const LevelContext = createContext(1); // 使用钩子创建上下文对象

                                           // Heading.js
                                                import {useContext} from 'react';
                                                import {LevelContext} from './LevelContext.js'; // 导入上下文类型

                                                export default function Heading({children}){
                                                    const level = useContext(LevelContext);    // 使用钩子读取上下文对象 
                                                    switch (level) {
                                                        case 1:
                                                        return <h1>{children}</h1>;
                                                        case 2:
                                                        return <h2>{children}</h2>;
                                                        case 3:
                                                        return <h3>{children}</h3>;
                                                        case 4:
                                                        return <h4>{children}</h4>;
                                                        case 5:
                                                        return <h5>{children}</h5>;
                                                        case 6:
                                                        return <h6>{children}</h6>;
                                                        default:
                                                        throw Error('Unknown level: ' + level);
                                                    }
                                                }

                                            // Section.js
                                                 import  {LevelContext} from './LevelContext.js'; // 导入上下文类型
                                                 export default function Section({level,children}){
                                                    return(  // 使用上下文提供程序封装子组件，以向子组件提供LevelContext
                                                        <section className="section">
                                                            <LevelContext.Provider value={level}>  
                                                                {children}
                                                            </LevelContext.Provider>
                                                        </section>
                                                    );
                                                 }

                                            // App.js
                                                import Heading from './Heading.js';
                                                import Section from './Section.js';

                                                export default function Page() {
                                                    return (
                                                        <Section level={1}>
                                                        <Heading>Title</Heading>
                                                        <Section level={2}>
                                                            <Heading>Heading</Heading>
                                                            <Heading>Heading</Heading>
                                                            <Heading>Heading</Heading>
                                                            <Section level={3}>
                                                            <Heading>Sub-heading</Heading>
                                                            <Heading>Sub-heading</Heading>
                                                            <Heading>Sub-heading</Heading>
                                                            <Section level={4}>
                                                                <Heading>Sub-sub-heading</Heading>
                                                                <Heading>Sub-sub-heading</Heading>
                                                                <Heading>Sub-sub-heading</Heading>
                                                            </Section>
                                                            </Section>
                                                        </Section>
                                                        </Section>
                                                    );
                                                }


                // 3、从同一组件使用和提供上下文
                            //      目前为止你仍然需要手动指定每个部分的level:（看看上面的代码，每个Section都指定了level属性）

                            /*
                                 由于上下文允许你从上面的组件读取信息，因此每个Section都可以从上面的Section读取Level,并自动向下传递 level+1.
                                 这是你可以如何做到的：            
                            */ 
                                // Section.js
                                        import {useContext} from 'react';
                                        import {LevelContext} from './LevelContext.js';

                                        export default function Section({children}){
                                            const level = useContext(LevelContext); // 先读取父组件的level值
                                            return (
                                                <section className="section">
                                                    <LevelContext.Provider value={level+1}> // 再将level+1提供给自己的子组件
                                                        {children}
                                                    </LevelContext.Provider>
                                                </section>
                                            );
                                        }

                                // App.js
                                        import Heading from './Heading.js';
                                        import Section from './Section.js';
                                        // 最外层 Section 在使用 useContext读取LevelContext时，因为没有别的组件提供LevelContext，所以会返回一个默认值。
                                        export default function Page() {
                                            return (
                                                <Section>
                                                    <Heading>Title</Heading>
                                                    <Section>
                                                        <Heading>Heading</Heading>
                                                        <Heading>Heading</Heading>
                                                        <Heading>Heading</Heading>
                                                        <Section>
                                                            <Heading>Sub-heading</Heading>
                                                            <Heading>Sub-heading</Heading>
                                                            <Heading>Sub-heading</Heading>
                                                            <Section>
                                                                <Heading>Sub-sub-heading</Heading>
                                                                <Heading>Sub-sub-heading</Heading>
                                                                <Heading>Sub-sub-heading</Heading>
                                                            </Section>
                                                        </Section>
                                                    </Section>
                                                </Section>
                                            );
                                        }


                            /*
                                现在 <Heading> 和 <Section> 都读取了LevelContext来弄清楚它们有多深。
                                Section将其子级封装到LevelContext中，以指定其中的任何内容都属于"更深"。
                            */

                // 四、中间组件传递上下文
                            /*
                                你可以在"提供上下文的组件"和"使用它的组件"之间插入"任意数量的组件"。
                                这包含内置组件,如<div> 和 你可能自己构建的组件。
                            */                    
                            // 下面的示例中，相同的Post组件在两个不同的嵌套级别渲染。请注意，其中的<Heading>自动的从最近的<Section>获取级别。
                                import Heading from './Heading.js';
                                import Section from './Section.js';

                                export default function ProfilePage() {
                                    return (
                                        <Section>
                                            <Heading>My Profile</Heading>
                                            <Post
                                                title="Hello traveller!"
                                                body="Read about my adventures."
                                            />
                                            <AllPosts />
                                        </Section>
                                    );
                                }

                                function AllPosts() {
                                    return (
                                        <Section>
                                            <Heading>Posts</Heading>
                                            <RecentPosts />
                                        </Section>
                                    );
                                }

                                function RecentPosts() {
                                    return (
                                        <Section>
                                            <Heading>Recent Posts</Heading>
                                            <Post
                                                title="Flavors of Lisbon"
                                                body="...those pastéis de nata!"
                                            />
                                            <Post
                                                title="Buenos Aires in the rhythm of tango"
                                                body="I loved it!"
                                            />
                                        </Section>
                                    );
                                }

                                function Post({ title, body }) {
                                    return (
                                        <Section isFancy={true}>
                                            <Heading>
                                                {title}
                                            </Heading>
                                            <p><i>{body}</i></p>
                                        </Section>
                                    );
                                }

                           /*
                                 你没有为此做任何特别的事情。Section指定其中树的上下文，因此你可以在任何地方插入<Heading>,并且它将具有正确的大小。
                           */

                           /*
                                 上下文使你可以编写"适应它们的环境"的组件，并根据它们被渲染的位置(或者换句话说，在哪个上下文)以不同的方式显示它们自己。

                                 上下文的工作方式可能会让你想起 "CSS属性继承"。
                                 在CSS中，你可以为<div>指定color:blue,并且其中的任何DOM节点，无论多深，都将继承该颜色，除非中间的某个其他DOM节点用 color:red 覆盖它。

                                 同样，在React中，覆盖来自上方的某些上下文的"唯一方法"是： "将子级封装到具有不同值的上下文中提供器中去"（如此就有了覆盖上下文的能力）。

                                 在CSS中，不同的属性(如 color 和 background-color)不会相互覆盖。你可以将<div>的color设置为red，而不影响background-color.

                                 同样，不同的React上下文不会互相覆盖。
                                 你使用createContext创建的每个上下文都与其他上下文"完全独立"，并使用和提供该特定上下文将组件连接在一起。
                                 一个组件可以毫无问题地使用或提供许多不同的上下文。
                           */


                // 五、使用上下文之前
                            /*
                                上下文非常诱人使用！ 然而，这也意味着它很容易被过度使用。
                                仅仅因为你需要深入传递一些属性并不意味着你应该将这些信息放入上下文。

                                在使用上下文之前，你应该考虑以下几个备选方案：
                                    1、从传递属性开始：
                                            如果你的组件并不简单，Name通过十几个组件传递十几个属性并不罕见。它可能感觉像"长途跋涉"，但它非常清楚哪些组件使用哪些数据！
                                            维护你代码的人会很高兴你已经使用属性明确了数据流向。

                                    2、提取组件并 将 JSX 作为 children 传递 给它们。
                                            如果你通过许多不使用该数据的中间组件层传递某些数据（并且仅将其进一步向下传递），
                                            这通常意味着你忘记沿途提取某些组件。例如，也许你将 posts 之类的数据属性传递给不直接使用它们的可视化组件，
                                            例如 <Layout posts={posts} />。而是，让 Layout 将 children 作为属性，并渲染 <Layout><Posts posts={posts} /></Layout>。
                                            这减少了指定数据的组件和需要数据的组件之间的层数。
                            */

                // 六、上下文用例
                            /*
                                主题：如果你的应用允许用户更改其外观（例如夜间模式），
                                    你可以将上下文提供器放在应用的顶部，并在需要调整其视觉外观的组件中使用该上下文。

                                当前账户：许多组件可能需要知道当前登录的用户。
                                        将它放在上下文中可以方便地在树中的任何位置读取它。
                                        某些应用还允许你同时操作多个账户（例如，以不同用户的身份发表评论）。
                                        在这些情况下，将 UI 的一部分封装到具有不同当前账户值的嵌套提供器中会很方便。

                                路由大多数路由解决方案在内部使用上下文来保存当前路由。
                                        这就是每个链接 “知道” 是否处于活动状态的方式。
                                        如果你构建自己的路由，你可能也想这样做。

                                管理状态：
                                        随着你的应用的增长，你最终可能会在靠近应用顶部的地方看到很多状态。
                                        下面的许多远程组件可能想要更改它。
                                        将 reducer 与上下文一起使用 经常管理复杂的状态并将其传递给远程组件而没有太多麻烦。
                            */


                            /*
                                
                            */

        </script>
    </body>
</html>